﻿Imports System.CodeDom.Compiler, System.Reflection, System.Text, Microsoft.CSharp

''' <summary>
''' 
''' </summary>
''' <remarks></remarks>
Public Enum LocalsMode
    PrivateClassFields
    MethodParameters
End Enum

''' <summary>
''' A System for Compiling Dynamically Code. Includes all needed Fluff like Assemblies and Local Members(Will not Grab the Local Members Current Value -__-)
''' </summary>
''' <remarks></remarks>
''' <features></features>
''' <stepthrough></stepthrough>
Public Class Compiler

#Region "       Properties              >>>"

#Region "       Complex     >>>"

    Private m_properties As List(Of String)
    ''' <summary>
    ''' 
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Private ReadOnly Property PropertyList() As List(Of String)
        Get
            If m_properties Is Nothing Then
                m_properties = New List(Of String)()
                Dim props = Locals.Where(Function(l) l.Member = MemberType.[Property]).ToList()

                Dim Format$ = "                    public {0} {1} [ {2} {3} ]"
                For Each p In props
                    Dim T1 = If(p.HasGetAccessor, "get { return m___this.GetPropertyContent<" & p.CompilableTypeName.ToString & ">(""" & p.CompilableName.ToString & """); }", "")
                    Dim T2 = If(p.HasSetAccessor, "set { m___this.SetPropertyContent(value, """ & p.CompilableName.ToString & """); }", "")
                    Dim Result$ = String.Format(Format, p.CompilableTypeName, p.CompilableName, T1, T2.Replace("[", "{").Replace("]", "}"))

                    m_properties.Add(Result)
                Next
            End If
            Return m_properties
        End Get
    End Property

    Private m_localFields As List(Of LocalMember)
    ''' <summary>
    ''' 
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Public ReadOnly Property LocalFields() As List(Of LocalMember)
        Get
            If m_localFields Is Nothing Then
                m_localFields = (From l In Locals
                                 Where l.CompilableName <> "m___this" AndAlso l.Member = MemberType.Field AndAlso Not l.Name.StartsWith("___") AndAlso Not l.Name.StartsWith("$")
                                 Select l).ToList()

            End If
            Return m_localFields
        End Get
    End Property

    Private m_fields As List(Of String)
    ''' <summary>
    ''' 
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Private ReadOnly Property FieldList() As List(Of String)
        Get
            If m_fields Is Nothing Then
                m_fields = GetFieldList(LocalsCreationMode)
            End If
            Return m_fields
        End Get
    End Property



    ''' <summary>
    ''' 
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Private ReadOnly Property ThisMember() As LocalMember
        Get
            Return (From l In Locals Where l.CompilableName = "m___this" Select l).FirstOrDefault()
        End Get
    End Property

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Public ReadOnly Property UnavailableLocalFields() As List(Of LocalMember)
        'wegen private / internal accessibility der verwendeten typen
        Get
            Return Locals.Where(Function(l) l.Member = MemberType.Field AndAlso Not l.Name.StartsWith("___")).ToList().FindAll(Function(l) Not LocalFields.Any(Function(lf) lf.Name = l.Name)).ToList()
        End Get
    End Property


    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="LocalsCreationMode"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    ''' <stepthrough></stepthrough>
    Private Function GetFieldList(ByVal LocalsCreationMode As LocalsMode) As List(Of String)
        If LocalsCreationMode = LocalsMode.MethodParameters Then
            Return New List(Of String)()
        Else
            Return (From l In LocalFields Select String.Format(("                    public {0} {1};"), l.CompilableTypeName, l.CompilableName)).ToList()
        End If
    End Function

#End Region

    ''' <summary>Represents the results of compilation that are returned from a compiler.</summary>
    ''' <remarks>This is the Primary Property Used in this Class. Do not Confuse it with OutputAssembly</remarks>
    Public Property Results As CompilerResults
    Public Property OutputAssembly As Assembly
    Private Property AssemblyReferences As List(Of Assembly)
    ''' <summary>The Code that will be Added into the Main Method and Later used</summary> 
    Private Property MethodBody As String
    ''' <summary>A Representation of all the Locals that will be Added</summary> 
    Private Property Locals As List(Of LocalMember)
    Private Property LocalsCreationMode() As LocalsMode

    Private m_outputAssemblyPath As String

    ''' <summary>
    ''' The Output Path of the <see cref="OutputAssembly">OutputAssembly</see>
    ''' </summary>
    ''' <value></value>
    ''' <remarks>If there is Currently no Path then <see cref="GetNewTempOutputPath">GetNewTempOutputPath</see> Is Used</remarks>
    Public Property OutputAssemblyPath() As String
        Get
            If m_outputAssemblyPath Is Nothing Then
                GetNewTempOutputPath()
            End If
            Return m_outputAssemblyPath
        End Get
        Set(ByVal value As String)
            m_outputAssemblyPath = value
        End Set
    End Property


    ''' <summary>
    ''' Returns the ErrorText From Each Error
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Public ReadOnly Property ErrorText() As String
        Get
            Dim errors As String = String.Empty
            For Each Item In Results.Errors.Cast(Of CompilerError).ToList
                errors = errors & Item.ErrorText & Environment.NewLine
            Next
            Return errors
        End Get
    End Property


    Public ReadOnly Property ErrorTextDetailed() As String
        Get
            Dim errors As String = String.Empty
            Results.Errors.Cast(Of CompilerError)().ToList().ForEach(Function(e) errors = errors & e.ToString() & Environment.NewLine)
            Return errors
        End Get
    End Property


    Private ReadOnly Property AssemblyFileNames() As String()
        Get
            Return AssemblyReferences.Select(Function(a) a.Location).ToArray()
        End Get
    End Property


    ''' <summary>
    ''' Returns the MethodInjector from the <see cref="OutputAssembly">OutputAssembly</see>
    ''' </summary>
    ''' <value></value>
    ''' <remarks>Code: <code>Return OutputAssembly.GetType("MethodInjector")</code></remarks>
    Public ReadOnly Property InjectorType() As Type
        Get
            Return OutputAssembly.GetType("MethodInjector")
        End Get
    End Property


    ''' <summary>
    ''' Do the Results have any Problems? 
    ''' Code: <code>Return (Results Is Nothing OrElse Results.Errors.HasErrors)</code>
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Public ReadOnly Property HasErrors() As Boolean
        Get
            Return (Results Is Nothing OrElse Results.Errors.HasErrors)
        End Get
    End Property

#End Region


#Region "       Template-Contents       >>>"

    Private m_usedNamespaces As List(Of String)
    Private ReadOnly Property UsedNamespaces() As List(Of String)
        Get
            If m_usedNamespaces Is Nothing Then
                m_usedNamespaces = (From a In AssemblyReferences
                     From t In a.GetTypes()
                     Where t.[Namespace] IsNot Nothing AndAlso t.[Namespace].Trim().Length > 0
                     Select t.[Namespace]).Distinct().ToList()
            End If
            Return m_usedNamespaces
        End Get
    End Property

    Private m_usings As StringBuilder
    Private ReadOnly Property Usings() As String
        Get
            If m_usings Is Nothing Then
                m_usings = New StringBuilder()
                UsedNamespaces.ForEach(Sub(u)
                                           If Not u.StartsWith("<") Then
                                               m_usings.Append("using ")
                                               m_usings.Append(u)
                                               m_usings.AppendLine(";")
                                           End If
                                       End Sub)
            End If
            Return m_usings.ToString()
        End Get
    End Property

    Private m_refParams As StringBuilder
    Private ReadOnly Property MethodParameters() As String
        Get
            If m_refParams Is Nothing Then
                m_refParams = New StringBuilder()
                If LocalsCreationMode = LocalsMode.MethodParameters Then
                    Dim localFields = Locals.Where(Function(l) l.Member = MemberType.Field AndAlso l.CompilableName <> "m___this" AndAlso Not l.Name.StartsWith("___") AndAlso Not l.Name.StartsWith("$")).ToList()
                    For i As Integer = 0 To localFields.Count - 1
                        m_refParams.Append("ref ")
                        m_refParams.Append(localFields(i).CompilableTypeName)
                        m_refParams.Append(" ")
                        m_refParams.Append(localFields(i).CompilableName)
                        If i < localFields.Count - 1 Then
                            m_refParams.Append(", ")
                        End If
                    Next
                End If
            End If
            Return m_refParams.ToString()
        End Get
    End Property

#End Region


#Region "       Utilities               >>>"

    ''' <summary>
    ''' Gives a Path to <see cref="m_outputAssemblyPath"/>
    ''' </summary>
    ''' <remarks>Code: <code> m_outputAssemblyPath = TempFileNameService.GetNewFileName("dll")</code></remarks>
    ''' <stepthrough>Enabled</stepthrough>
    <DebuggerNonUserCode()>
    Private Sub GetNewTempOutputPath()
        m_outputAssemblyPath = TempFileNameService.GetNewFileName("dll")
    End Sub

    ''' <summary>Converts A List(Of String) into A String by Combining them one After Another</summary> 
    <DebuggerStepThrough()>
    Private Function GetAllLinesFromList(ByVal list As List(Of String)) As String
        Dim sb As New StringBuilder()
        For Each s As String In list
            sb.AppendLine(s)
        Next
        Return sb.ToString()
    End Function


#End Region



    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="methodBody__1"></param>
    ''' <param name="referencedAssemblies"></param>
    ''' <param name="localReachableMembers"></param>
    ''' <param name="buildMode"></param>
    ''' <remarks></remarks>
    ''' <stepthrough></stepthrough>
    Public Sub New(ByVal methodBody__1 As String, ByVal referencedAssemblies As List(Of Assembly), ByVal localReachableMembers As List(Of LocalMember), ByVal buildMode As LocalsMode)
        MethodBody = methodBody__1
        AssemblyReferences = referencedAssemblies
        If Not localReachableMembers.Any(Function(l) l.Name = "m___this") Then
            localReachableMembers.Add(New LocalMember("m___this", "System.Object", MemberType.Field, GetType(Object)))
        End If
        Locals = localReachableMembers
        LocalsCreationMode = buildMode
    End Sub


    ''' <summary>
    ''' 
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    ''' <stepthrough></stepthrough>
    Private Function CheckForUnreachableTypes() As CompilerResults
        Dim propertySection As String = GetAllLinesFromList(PropertyList)
        Dim fieldsSection As String = GetAllLinesFromList(GetFieldList(LocalsMode.PrivateClassFields))
        Dim results As CompilerResults = CompileInternal(propertySection, fieldsSection, "")

        'If errors have occurred in the property declaration
        'bric (accessibility of type), try the Properties
        If results.Errors.HasErrors Then
            Dim propertyListStartLine As Integer = UsedNamespaces.Count + 3
            Dim propertyListEndLine As Integer = propertyListStartLine + PropertyList.Count
            Dim fieldListStartLine As Integer = propertyListEndLine + 1
            Dim fieldListEndLine As Integer = fieldListStartLine + FieldList.Count
            Dim errorsOutsideLocalsListDetected As Boolean = False

            For Each e In results.Errors.Cast(Of CompilerError)()
                If e.Line < propertyListStartLine OrElse e.Line > fieldListEndLine Then
                    errorsOutsideLocalsListDetected = True
                    Exit For
                End If
            Next

            If Not errorsOutsideLocalsListDetected Then
                For Each e In results.Errors.Cast(Of CompilerError)()
                    Dim indexWithinPropertyList As Integer = e.Line - propertyListStartLine - 1
                    Dim indexWithinFieldList As Integer = e.Line - fieldListStartLine - 1
                    If indexWithinPropertyList >= 0 AndAlso indexWithinPropertyList < PropertyList.Count Then
                        PropertyList.RemoveAt(indexWithinPropertyList)
                    End If
                    If indexWithinFieldList >= 0 AndAlso indexWithinFieldList < FieldList.Count Then
                        FieldList.RemoveAt(indexWithinFieldList)
                    End If
                    propertySection = GetAllLinesFromList(PropertyList)
                    fieldsSection = GetAllLinesFromList(FieldList)
                    GetNewTempOutputPath()
                    results = CompileInternal(propertySection, fieldsSection, "")
                Next
            End If
        End If

        Return results
    End Function

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="propertySection"></param>
    ''' <param name="fieldsSection"></param>
    ''' <param name="methodParams"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    ''' <stepthrough></stepthrough>
    Private Function CompileInternal(ByVal propertySection As String, ByVal fieldsSection As String, ByVal methodParams As String) As CompilerResults
        Dim csc As New CSharpCodeProvider(Mod_Utilities.Versioning.CodeProviderSetting)
        Dim Core = System.Reflection.Assembly.GetAssembly(GetType(System.Security.ManifestKinds))
        If Not Me.AssemblyReferences.Exists(Function(x) x.FullName = Core.FullName) Then 'Adds an Extra DLL that is needed!
            Me.AssemblyReferences.Add(Core)
        End If

        Dim parameters As New CompilerParameters(AssemblyFileNames, OutputAssemblyPath)
        parameters.GenerateExecutable = False
        'parameters.GenerateInMemory = true;
        parameters.IncludeDebugInformation = False


        Dim Text$ = My.Resources.CodeFormat.Replace("{0}", Usings).Replace("{1}", propertySection).Replace("{2}", fieldsSection)
        Text = Text.Replace("{3}", methodParams).Replace("{4}", MethodBody)

        Dim result = csc.CompileAssemblyFromSource(parameters, Text)
        If Not result.Errors.HasErrors Then
            OutputAssembly = result.CompiledAssembly
        End If

        Return result
    End Function

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    ''' <stepthrough></stepthrough>
    Public Function Compile() As Boolean
        Dim Result As Boolean '<-- Aaron Made
        Dim results__1 As CompilerResults = CheckForUnreachableTypes()

        If results__1.Errors.HasErrors Then                             'Check whether there GIBTM types are unavailable, because private, or internal, if necessary, throw out
            Results = results__1
            Result = False
        ElseIf LocalsCreationMode = LocalsMode.PrivateClassFields Then  'If Build mode corresponded to our successful and to use as the return
            OutputAssemblyPath = results__1.PathToAssembly
            Results = results__1
            Result = True
        Else                                                            'If a different set mode (method parameter), then again with other Sessings Builder
            GetNewTempOutputPath()
            Dim propertySection As String = GetAllLinesFromList(PropertyList)
            Dim fieldsSection As String = GetAllLinesFromList(FieldList)
            Results = CompileInternal(propertySection, fieldsSection, MethodParameters)
            Result = Not Results.Errors.HasErrors  ' Then
        End If

        m_localFields = Nothing
        m_properties = Nothing
        m_localFields = Nothing
        m_fields = Nothing
        m_refParams = Nothing

        Return Result
    End Function


End Class




' ''' <summary>
' ''' work on this
' ''' </summary>
' ''' <remarks></remarks>
' ''' <features></features>
' ''' <stepthrough></stepthrough>
'Public Class CodeClass
'    Public Property Usings As String
'    Public Property PropertySection As String
'    Public Property FieldSection As String
'    Public Property MethodParams As String
'    Public Property MethodBody As String

'    ''' <summary>
'    ''' 
'    ''' </summary>
'    ''' <value></value>
'    ''' <remarks></remarks>
'    Public ReadOnly Property Code As String
'        Get
'            Dim Text$ = My.Resources.CodeFormat.Replace("{0}", Usings).Replace("{1}", PropertySection).Replace("{2}", FieldSection)
'            Return Text.Replace("{3}", MethodParams).Replace("{4}", MethodBody)
'        End Get
'    End Property


'    ''' <summary>
'    ''' 
'    ''' </summary>
'    ''' <param name="Usings"></param>
'    ''' <param name="PropertySection"></param>
'    ''' <param name="FieldSection"></param>
'    ''' <param name="MethodParams"></param>
'    ''' <param name="MethodBody"></param>
'    ''' <remarks></remarks>
'    ''' <stepthrough></stepthrough>
'    Sub New(ByVal Usings As String, ByVal PropertySection As String, ByVal FieldSection As String, ByVal MethodParams As String, ByVal MethodBody As String)
'        Me.Usings = Usings
'        Me.PropertySection = PropertySection
'        Me.FieldSection = FieldSection
'        Me.MethodParams = MethodParams
'        Me.MethodBody = MethodBody
'    End Sub


'    ''' <summary>
'    ''' 
'    ''' </summary>
'    ''' <remarks></remarks>
'    ''' <stepthrough></stepthrough>
'    Public Sub AddLocalsFromStackFrame()
'        Dim Text As String = My.Resources.CodeFormat
'        For Each Item In Mod_Utilities.Locals





'            Item.ToString()
'        Next
'    End Sub



'End Class